function foo(arg: any): any{
  return arg;
}
// function bar(arg: string): string{
//   return arg;
// }
const m: number = foo(5);
// 如果用any定义未来可能使用的类型,那么会导致类型不统一也无法检测的bug
// 所以我们希望有一种方法 可以定义某种类型,而且使类型统一.
const n: string = foo(5);
console.log('m',m);
console.log('n',n);

// 泛型可以解决 ,泛型是一种特殊的类型,可以用T来表示
// <T> 约束bar函数 接收泛型 ,对未来的类型进行约束
function bar<T>(arg: T): T{
  return arg;
}
const  x: number = bar<number>(5);
// 以下会报错 类型不统一
// const  x: string = bar<number>(5);
const y: string = bar<string>('abc');

console.log(x,y);

// 使用泛型变量 泛型是对未来类型的约束,至少不能出现可预见的错误
function loggingIdentity<T>(arg: T[]): T {
  console.log(arg.length);  // Error: T doesn't have .length
  return arg[0];
}
const z: string = loggingIdentity<string>(['a','b']);
console.log(z);

// 泛型函数类型
const logFn: <T>(arg: T[]) => T  = loggingIdentity;
// 或者用字面量形式定义
// const logFn: {<T>(arg: T[]): T}  = loggingIdentity;
logFn(['abc']);
// 单独封装类型接口  用于声明泛型函数类型的接口
interface ILogFn {
  <T>(arg: T[]): T
}
// 并不知道未来会是什么类型
const logFn2: ILogFn = loggingIdentity;
const logFn22: ILogFn = loggingIdentity;
console.log(logFn2(['abc']));
console.log(logFn22([123]));
// 在接口外部直接体现泛型 可以把泛型T往外提,使我们的泛型接口更加精确
interface ILogFnn<T> {
  (arg: T[]): T
}
const logFn3: ILogFnn<number> = loggingIdentity;
const logFn4: ILogFnn<string> = loggingIdentity;
console.log(logFn3([1,2,4]));
console.log(logFn4(['abc']));
// 引申 Array类 也使用了泛型定义,为了保证未来的类型统一
// const arr: Array<number> = [1,2,3];

// 泛型类 class和interface区别,interface可以只声明不赋值,但是class必须声明赋值. 
class GenericNumber<T> {
  zeroValue: T;
  add: (x: T, y: T) => T = ()=> this.zeroValue;
  constructor(arg: T){
    this.zeroValue = arg;
  }
}
// myGenericNumber实例对象
const myGenericNumber = new GenericNumber<number>(1);
myGenericNumber.zeroValue = 0;
myGenericNumber.add(1,2);

// 或者用其他的类型限定
const stringNumeric = new GenericNumber<string>('abc');
stringNumeric.zeroValue = "";
stringNumeric.add('a','b');

console.log(stringNumeric.add(stringNumeric.zeroValue, "test"));
// 泛型约束
// 肯定希望T存在length属性 为了保证T肯定存在length属性,可以对T进行约束
interface loggingT{
  length: number;
}
// T 是符合 loggingT类型的,所以肯定是有length属性的
function loggingIdentity2<T extends loggingT>(arg: T): T {
  console.log(arg.length);  // Error: T doesn't have .length
  return arg;
}
// 一个字面量对象 符合 接口loggingT的类型 可以作为loggingIdentity2参数传递
const logObj: loggingT = {
  length: 10
}
loggingIdentity2(logObj);
// Log 实现接口 loggingT ,那么Log就符合接口类型
class Log implements loggingT {
  length: number;
  constructor(num: number){
    this.length = num;
  }
}
// 得到实例对象
const logInstance: Log = new Log(10);
const rs: Log =  loggingIdentity2<Log>(logInstance);
console.log(rs);
